#include "shell.h"
#include "assert.h"
#include "file.h"
#include "syscall.h"
#include "stdio.h"
#include "string.h"
#include "buildin_cmd.h"
#include "exec.h"

// #define cmd_len 128 // 最大支持128个字符命令输入
#define MAX_ARG_NR 16 // 加上命令名外，最多支持15个参数

static char cmd_line[MAX_PATH_LEN] = {0}; // 记录输入的命令
char final_path[MAX_PATH_LEN] = {0};
char cwd_cache[MAX_PATH_LEN] = {0}; // 记录当前目录，是当前目录的缓存，执行cd时会更新此内容

/* 输出提示符 */
void print_prompt(void) {
    printf("[windsgo@localhost %s]$ ", cwd_cache);
}

/* 从键盘缓冲区中最多读入count个字节到buf */
static void readline(char *buf, int32_t count) {
    assert(buf != NULL && count > 0);
    char *pos = buf;
    while (read(stdin_no, pos, 1) != -1 && (pos - buf) < count) {
        switch (*pos)
        {
        case '\n':
        case '\r':
            *pos = 0; // 添加cmd_line的终止字符
            putchar('\n');
            return;

        case '\b':
            if (buf[0] != '\b') {
                // 阻止删除非本次输入信息
                --pos;
                putchar('\b');
            }
            break;

        case 'l' - 'a': // ctrl+l 清屏
            *pos = 0; // 将当前的字符'l' - 'a' 置0
            clear(); // 清空屏幕
            print_prompt(); // 打印提示符
            printf("%s", buf); // 将键入的内容再打印
            break;
        
        case 'u' - 'a': // ctrl+u 清空输入
            while (buf != pos) {
                putchar('\b');
                *(pos--) = 0;
            }
            break;

        default:
            putchar(*pos);
            pos++;
        }
    }
    printf("readline: can\'t find enter_key in the cmdline, max num of char is 128\n");
}

/* 分析字符串cmd_str中以token为分隔符的单词，将各单词指针存入argv */
static uint32_t cmd_parse(char *cmd_str, char **argv, char token) {
    assert(cmd_str != NULL);
    int32_t arg_idx = 0;
    while (arg_idx < MAX_ARG_NR) {
        argv[arg_idx] = NULL;
        arg_idx++;
    }
    char *next = cmd_str;
    int32_t argc = 0;
    while (*next) {
        while (*next == token) {
            // 去除命令字和参数之间的token
            next++;
        }
        if (*next == 0) {
            // 处理最后一个参数后接空格的情况
            break;
        }
        argv[argc] = next;

        while (*next && *next != token) {
            // 在字符串结束前找单词分隔符
            next++;
        }

        // 如果未结束（是token），使token变0
        if (*next) {
            *next++ = 0; // 作为一个单词的结束
        }

        // 避免argv数组越界, 参数过多则返回0
        if (argc > MAX_ARG_NR) {
            return -1;
        }
        argc++;
    }
    return argc;
}

char *argv[MAX_ARG_NR];

int32_t argc = -1;

/* 简单的shell */
void my_shell(void) {
    cwd_cache[0] = '/';
    
    while (1)
    {
        print_prompt();
        memset(final_path, 0, MAX_PATH_LEN);
        memset(cmd_line, 0, MAX_PATH_LEN);
        readline(cmd_line, MAX_PATH_LEN); // 等待键盘输入回车
        if (cmd_line[0] == 0) {
            // 只是键入了回车
            continue;
        }
        argc = -1;
        argc = cmd_parse(cmd_line, argv, ' ');
        if (argc == -1) {
            printf("num of arguments exceed %d\n", MAX_ARG_NR);
            continue;
        }

        if (!strcmp("ls", argv[0]))
        {
            buildin_ls(argc, argv);
        }
        else if (!strcmp("cd", argv[0]))
        {
            if (buildin_cd(argc, argv) != NULL)
            {
                memset(cwd_cache, 0, MAX_PATH_LEN);
                strcpy(cwd_cache, final_path);
            }
        }
        else if (!strcmp("pwd", argv[0]))
        {
            buildin_pwd(argc, argv);
        }
        else if (!strcmp("ps", argv[0]))
        {
            buildin_ps(argc, argv);
        }
        else if (!strcmp("clear", argv[0]))
        {
            buildin_clear(argc, argv);
        }
        else if (!strcmp("mkdir", argv[0]))
        {
            buildin_mkdir(argc, argv);
        }
        else if (!strcmp("rmdir", argv[0]))
        {
            buildin_rmdir(argc, argv);
        }
        else if (!strcmp("rm", argv[0]))
        {
            buildin_rm(argc, argv);
        }
        else
        {
            int32_t pid = fork();
            if (pid)
            { // 父进程
                /* 下面这个while必须要加上,否则父进程一般情况下会比子进程先执行,
	    因此会进行下一轮循环将findl_path清空,这样子进程将无法从final_path中获得参数*/
                while (1)
                    ;
            }
            else
            { // 子进程
                make_clear_abs_path(argv[0], final_path);
                argv[0] = final_path;
                /* 先判断下文件是否存在 */
                struct stat file_stat;
                memset(&file_stat, 0, sizeof(struct stat));
                if (stat(argv[0], &file_stat) == -1)
                {
                    printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
                }
                else
                {
                    execv(argv[0], argv);
                }
                while (1)
                    ;
            }
        }
        int32_t arg_idx = 0;
        while (arg_idx < MAX_ARG_NR)
        {
            argv[arg_idx] = NULL;
            arg_idx++;
        }
    }
    panic("myshell: should not be here");
}